// file:system/fs/diskman.c
// autor:jiangxinpeng
// time: 2021.4.18
// copyright: (C) 2020-2050 by jiangxinpeng,All right are reserved.

#include <os/diskman.h>
#include <os/driver.h>
#include <os/mutexlock.h>
#include <os/debug.h>
#include <os/safety.h>
#include <lib/errno.h>
#include <lib/list.h>
#include <lib/type.h>
#include <lib/stdio.h>
#include <sys/res.h>

// disk manager
// manage all disk and volume
disk_manager_t diskman;

// global dirver map[vol:diskid]
int driver_map[DISK_MAX] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

// disk solt handle
static int disk_solt_cache[DISK_SOLT_NUM];
DEFINE_MUTEX_LOCK(disk_manager_mutex);
// disk info list head
LIST_HEAD(disk_list_head);
// alloc disk solt number
static int disk_solt = 0;
static int disk_next_id = 0;

#define IS_BAD_SOLT(solt) ((solt) < 0 || (solt) > DISK_SOLT_NUM)
#define SOLT_TO_HANDLE(solt) disk_solt_cache[solt]

static int DiskManagerOpen(int solt)
{
        disk_t *disk;

        if (IS_BAD_SOLT(solt))
                return -1;
        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (disk->solt == solt)
                {
                        if (!AtomicGet(&disk->ref))
                        {
                                disk->handle = DeviceOpen(disk->devtern.dev_name, 0);
                                if (disk->handle < 0)
                                {
                                        MutexlockUnlock(&disk_manager_mutex);
                                        return -1;
                                }
                                disk_solt_cache[solt] = disk->handle;
                        }
                        AtomicInc(&disk->ref);
                        MutexlockUnlock(&disk_manager_mutex);
                        return 0;
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return -1;
}

static int DiskManagerClose(int solt)
{
        disk_t *disk;

        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (disk->solt == solt)
                {
                        // disk only open once
                        if (AtomicGet(&disk->ref) == 1)
                        {
                                AtomicDec(&disk->ref);
                                if (DeviceClose(disk->handle))
                                {
                                        MutexlockUnlock(&disk_manager_mutex);
                                        return -1;
                                }
                                // delect solt cache handle
                                for (int i = 0; i < DISK_SOLT_NUM; i++)
                                {
                                        if (disk_solt_cache[i] == disk->handle)
                                        {
                                                disk_solt_cache[i] = -1;
                                                break;
                                        }
                                }
                                disk->handle = -1;
                        }
                        else
                        {
                                // disk no open
                                if (!AtomicGet(&disk->ref))
                                {
                                        MutexlockUnlock(&disk_manager_mutex);
                                        return -1;
                                }
                                else
                                {
                                        AtomicDec(&disk->ref);
                                        MutexlockUnlock(&disk_manager_mutex);
                                        return 0;
                                }
                        }
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return -1;
}

static int DiskManagerRead(int solt, offset_t off, buffer_t buffer, size_t size)
{
        if (IS_BAD_SOLT(solt))
                return -1;
        if (DeviceRead(SOLT_TO_HANDLE(solt), buffer, size, off) < 0)
        {
                return -1;
        }
        return 0;
}

static int DiskManagerWrite(int solt, offset_t off, buffer_t buffer, size_t size)
{
        if (IS_BAD_SOLT(solt))
                return -1;
        if (DeviceWrite(SOLT_TO_HANDLE(solt), buffer, size, off) < 0)
                return -1;
        return 0;
}

static int DiskManagerIoCtl(int solt, int cmd, void *arg)
{
        if (IS_BAD_SOLT(solt))
                return -1;
        if (DeviceDevCtl(SOLT_TO_HANDLE(solt), cmd, arg) < 0)
                return -1;
        return 0;
}

// translate devfs path to device name
// for example DEVFS_PATH/XXX -> XXX
static void *DiskManPathTranslate(const char *path)
{
        char *p = (char *)path;

        if (!path)
                return NULL;
        // check if is devfs
        if (strncmp(path, DEVFS_PATH, sizeof(DEVFS_PATH)) < 0)
                return NULL;
        p += strlen(DEVFS_PATH);

        while (*p && *p == '/')
                p++;

        KPrint("path translate %s\n", p);
        return p;
}

int DiskManagerInit()
{
        // init disk solt cache area
        for (int i = 0; i < DISK_SOLT_NUM; i++)
                disk_solt_cache[i] = -1;

        // bind diskmanager operator interface
        diskman.open = DiskManagerOpen;
        diskman.close = DiskManagerClose;
        diskman.read = DiskManagerRead;
        diskman.write = DiskManagerWrite;
        diskman.ioctl = DiskManagerIoCtl;

        list_init(&disk_list_head);

        KPrint("[diskman] init diskman ok\n");
}

int DiskAdd(device_object_t *device, uint8_t type)
{
        disk_t *disk;
        devent_t dev;

        // make device entry
        strcpy(dev.dev_name, device->name.text);
        dev.dev_type = (type == DISK_TYPE_DISK) ? DEVICE_TYPE_DISK : DEVICE_TYPE_VOL;

        // alloc disk info
        disk = (disk_t *)KMemAlloc(sizeof(disk_t));
        if (!disk)
                return -1;

        disk->id = disk_next_id++;
        disk->devtern = dev;
        disk->handle = -1;
        sprintf(disk->virname, "disk%d", disk_solt);
        disk->solt = disk_solt++;
        disk->type = type; // disk type
        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_add_tail(&disk->list, &disk_list_head);
        MutexlockUnlock(&disk_manager_mutex);
        KPrint("[diskman] disk %s register! type %d solt %d\n", device->name.text, type, disk->solt);
        return 0;
}

int DiskDel(device_object_t *device)
{
        disk_t *disk = DiskFind(device->name.text);
        if (!disk)
                return -1;

        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_del_init(&disk->list);
        MutexlockUnlock(&disk_manager_mutex);
        KMemFree(disk);
        KPrint("[diskman] disk %s unregister! type %d\n", device->name.text, disk->type);
        return 0;
}

void DiskInfoPrint()
{
        disk_t *disk;

        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                KPrint(PRINT_INFO "[diskman]: disk device:%s virname:%s solt:%d type %d\n", disk->devtern.dev_name, disk->virname, disk->solt, disk->type);
        }
        MutexlockUnlock(&disk_manager_mutex);
}

int DiskSoltFind(const char *name)
{
        disk_t *disk;

        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (!strcmp(disk->virname, name) || !strcmp(disk->devtern.dev_name, name))
                {
                        MutexlockUnlock(&disk_manager_mutex);
                        return disk->solt;
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return -1;
}

int DiskSoltFindByPath(const char *path)
{
        char *name = DiskManPathTranslate(path);
        if (!name)
                return -1;
        return DiskSoltFind(name);
}

int DiskSoltFindById(int id)
{
        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        disk_t *disk;
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (disk->id == id)
                {
                        MutexlockUnlock(&disk_manager_mutex);
                        return disk->solt;
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return -1;
}

// return physical disk solt passed logical disk id
int DiskSoltFindDiskById(int id)
{
        disk_t *disk = DiskFindById(id);
        if (!disk)
                return -1;

        if (disk->type == DISK_TYPE_DISK) // if device is disk,direct return solt
        {
                KPrint("[disk] found disk on solt %d\n", disk->solt);
                return disk->solt;
        }

        char name[DEVICE_NAME_LEN+1]; // translate volume device name to disk device name
        strcpy(name, disk->devtern.dev_name);
        char *p = name;
        while (*p++ != 'p')
                ;
        *p = '\0';
        disk = DiskSoltFind(name); // found targe disk and return disk solt
        if (!disk)
                return -1;
        KPrint("[disk] found disk on solt %d\n", disk->solt);
        return disk->solt;
}

int DiskIsValid(int solt)
{
        disk_t *disk;
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (disk->solt == solt)
                        return 1;
        }
        return 0;
}

int DiskSoltFindVolById(int id)
{
        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        disk_t *disk;
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (disk->id == id && disk->type == DEVICE_TYPE_VOL)
                {
                        MutexlockUnlock(&disk_manager_mutex);
                        return disk->solt;
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return -1;
}

disk_t *DiskFind(char *name)
{
        disk_t *disk;
        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (!strcmp(disk->devtern.dev_name, name))
                {
                        MutexlockUnlock(&disk_manager_mutex);
                        return disk;
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return NULL;
}

disk_t *DiskFindBySolt(int solt)
{
        disk_t *disk;
        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (disk->solt == solt)
                {
                        MutexlockUnlock(&disk_manager_mutex);
                        return disk;
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return NULL;
}

disk_t *DiskFindById(int id)
{
        disk_t *disk;
        MutexlockLock(&disk_manager_mutex, MUTEX_LOCK_MODE_BLOCK);
        list_traversal_all_owner_to_next(disk, &disk_list_head, list)
        {
                if (disk->id == id)
                {
                        MutexlockUnlock(&disk_manager_mutex);
                        return disk;
                }
        }
        MutexlockUnlock(&disk_manager_mutex);
        return NULL;
}

// return disk info by disk device name
int SysDiskInfoGet(char *name, disk_t *buff)
{
        KPrint("get disk %s info\n", name);

        if (SafetyCheckRange(name, strlen(name)) < 0)
                return -EINVAL;
        if (SafetyCheckRange(buff, sizeof(disk_t)) < 0)
                return -EINVAL;

        disk_t *disk = DiskFind(name);
        if (!disk)
                return -ENODEV;

        if (MemCopyToUser(buff, disk, sizeof(disk_t)) < 0)
                return -EACCES;

        disk=(disk_t*)buff;
        KPrint("disk info id %d name %s\n",buff->id,buff->virname);

        KPrint("get disk info ok\n");
        return 0;
}
